package com.qd.upload.service;

import com.cdqidi.exception.ApiException;
import com.cdqidi.util.FileUtil;
import com.cdqidi.util.text.SpringFileUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qd.upload.config.FileConfirmReq;
import com.qd.upload.config.UploadDTO;
import com.qd.upload.constant.FileConfirmDTO;
import com.qd.upload.constant.FileDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author sjk
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UploadServiceImpl implements UploadConfirmService {
    private static final List<String> FILE_TYPES = Arrays.asList("media", "img", "image", "file");
    private final FileAsyncService fileAsyncService;
    private final UploadDTO uploadDto;
    private final ObjectMapper objectMapper;
    private final StringRedisTemplate stringRedisTemplate;

    @Override
    public List<FileConfirmDTO> fileConfirms(FileConfirmReq fileConfirmReq) {
        checkInfo(fileConfirmReq);
        final List<String> fileIds = fileConfirmReq.getFiles();
        final List<FileConfirmDTO> list = new ArrayList<>(fileIds.size());
        try {
            FileConfirmDTO confirm;
            for (String fileId : fileIds) {
                confirm = fileConfirm(fileId, fileConfirmReq.getInfo());
                list.add(confirm);
            }
            return list;
        } catch (Exception e) {
            throw new ApiException(e);
        }
    }

    @Override
    public FileConfirmDTO singleFileConfirm(FileConfirmReq fileConfirmReq) {
        checkInfo(fileConfirmReq);
        final List<String> files = fileConfirmReq.getFiles();
        if (files.size() > 1) {
            throw new ApiException("单个确认接口不支持多文件确认");
        }
        String fileId = files.get(0);
        return fileConfirm(fileId, fileConfirmReq.getInfo());
    }

    public String saveWxPic(String fullName, InputStream fileStream) {
        final String basePath = createBasePath(null, uploadDto.getOfficialPath());
        try {
            FileUtil.createFile(FileUtil.inputStreamToByte(fileStream), uploadDto.getOfficialPath() + basePath, fullName);
        } catch (IOException e) {
            throw new ApiException(e);
        }
        return basePath + File.separator + fullName;
    }

    private void checkInfo(FileConfirmReq fileConfirmReq) {
        final FileConfirmReq.Info info = fileConfirmReq.getInfo();
        final List<String> files = fileConfirmReq.getFiles();
        if (null == files || files.isEmpty()) {
            throw new ApiException("files不能为空");
        }
        if (!StringUtils.hasLength(info.getAttachmentType())) {
            throw new ApiException("文件类型不能为空");
        }
        if (!FILE_TYPES.contains(info.getAttachmentType())) {
            throw new ApiException("文件类型值错误[" + info.getAttachmentType() + "]");
        }
    }

    private FileConfirmDTO fileConfirm(String fileId, FileConfirmReq.Info info) {
        final String s = stringRedisTemplate.opsForValue().get(fileId);
        if (!StringUtils.hasLength(s)) {
            log.info("文件id无效，fileId: {}", fileId);
            throw new ApiException("文件id无效，请重新上传");
        }
        try {
            final FileDTO fileDto = objectMapper.readValue(s, FileDTO.class);
            final String basePath = createBasePath(info, uploadDto.getOfficialPath());
            final String fileName = fileDto.getFileId() + fileDto.getSuffixName();
            final String tempFile = fileDto.getTempParentPath() + File.separator + fileDto.getTempPath() + File.separator + fileName;
            final String officialFile = uploadDto.getOfficialPath() + File.separator + basePath + File.separator + fileName;
            FileCopyUtils.copy(new File(tempFile), new File(officialFile));
            final FileConfirmDTO fileConfirmDto = new FileConfirmDTO();
            fileConfirmDto.setFileId(fileId);
            fileConfirmDto.setFileSize(fileDto.getFileSize());
            fileConfirmDto.setFileName(fileDto.getFileName());
            fileConfirmDto.setTempFile(tempFile);
            fileConfirmDto.setOfficialFile(basePath + File.separator + fileName);
            fileConfirmDto.setDownloadPath(getFilePath(fileConfirmDto.getOfficialFile(), fileDto.getFileName()));
            fileDto.freeData();
            return fileConfirmDto;
        } catch (IOException e) {
            throw new ApiException(e);
        }
    }

    public List<String> uploadFiles(MultipartFile[] files) {
        final List<String> list = new ArrayList<>(files.length);
        String fileId;
        for (MultipartFile file : files) {
            fileId = uploadFile(file);
            list.add(fileId);
        }
        log.info("uploadFiles,list: {}", list);
        return list;
    }

    public String uploadFile(MultipartFile file) {
        final String suffixName = SpringFileUtil.getFileSuffixName(file);
        final String fileName = UUID.randomUUID().toString();
        final FileDTO dto = new FileDTO();
        dto.setFileId(fileName);
        dto.setFileName(file.getOriginalFilename());
        dto.setSuffixName(suffixName);
        dto.setFileSize(file.getSize());
        dto.setTempParentPath(uploadDto.getTempPath());
        dto.setTempPath(getTempFilePath());

        final String path = uploadDto.getTempPath() + dto.getTempPath();
        final File dest = new File(path, fileName + suffixName);
        try {
            file.transferTo(dest);
            final String json = objectMapper.writeValueAsString(dto);
            stringRedisTemplate.opsForValue().set(fileName, json, 1, TimeUnit.DAYS);
            return fileName;
        } catch (Exception e) {
            dto.freeData();
            throw new ApiException(e);
        }
    }

    private String getTempFilePath() {
        if (!StringUtils.hasLength(uploadDto.getTempPath())) {
            throw new ApiException("配置上传文件临时目录");
        }
        return createBasePath(null, uploadDto.getTempPath());
    }

    private String createBasePath(FileConfirmReq.Info info, String basePath) {
        String path = "";
        File file;
        if (null != info) {
            final String fileType = info.getAttachmentType();
            if (StringUtils.hasLength(fileType)) {
                path = path + File.separator + fileType;
            }
            if (StringUtils.hasLength(info.getTypeKey())) {
                final String[] typeKeys = info.getTypeKey().split("\\|");
                StringBuilder basePathBuilder = new StringBuilder(path);
                for (String key : typeKeys) {
                    basePathBuilder.append(File.separator).append(key);
                }
                path = basePathBuilder + File.separator;
            }
        }
        final LocalTime localDateTime = LocalTime.now();
        final LocalDate localDate = LocalDate.now();
        final String filePath = localDate.getYear() +
                File.separator + localDate.getMonth().getValue() +
                File.separator + localDate +
                File.separator + localDateTime.getHour() +
                File.separator;
        if (!path.endsWith("/") && !path.startsWith("//")) {
            path = path + File.separator;
        }
        path = path + filePath;
        if (null != info) {
            final String bKey = info.getBKey();
            if (StringUtils.hasLength(bKey)) {
                final String[] bKeys = bKey.split("\\|");
                StringBuilder basePathBuilder = new StringBuilder(path);
                for (String key : bKeys) {
                    basePathBuilder.append(File.separator).append(key);
                }
                path = basePathBuilder.toString();
            }
        }
        file = new File(basePath + File.separator + path);
        if (!file.isDirectory()) {
            if (!file.mkdirs()) {
                throw new ApiException("创建文件保存路径失败: " + file.getPath());
            }
        }
        return new File(path).getPath();
    }

    public void delete() {
        fileAsyncService.deleteDirectory();
    }

    /**
     * 文件
     * 拼装下载文件的绝对路径。基于磁盘的下载需要加上前缀路径，后续如果使用S3这里不做操作，直接返回路径
     *
     * @param path     路径
     * @param fileName 文件名
     * @return 绝对路径
     */
    private String getFilePath(String path, String fileName) {
        if (StringUtils.hasLength(path)) {
            path = path.replace("\\", "/");
            String imgDomain = uploadDto.getFileDomain();
            return imgDomain + "?path=" + URLEncoder.encode(path, StandardCharsets.UTF_8) + "&fileName=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8);
        } else {
            path = "";
        }
        return path;
    }

}
